Title Banner


Technical Q&A's


GX 07 - Embedding a GX Picture into a PICT (15-Sept-95)


Q Is there a QuickTime codec for converting QuickDraw GX pictures to QuickDraw PICT format? If so, can you provide this?

A At a WWDC '95 session, a new technique for exporting GX pictures as QuickDraw PICT files was demonstrated. The method makes use of QuickTime and a new codec (COmpressor-DECompressor) which is included in the GX extension version 1.1 and greater. By using this codec, one can embed a flattened GX picture into a PICT file (or a QuickTime movie). We recommend that you use this method if you want to allow your GX application to exchange pictures with existing QuickDraw applications.

One important feature of this codec is that does not convert QuickDraw GX pictures to QuickDraw PICT in the traditional sense of the word "convert." What this codec allows is the embedding of GX objects inside a PICT file format. The advantage of this is that it allows GX pictures to be viewed (but not edited) in any application that can open a PICT file. Although "embedding" is very useful, it is quite different from "conversion."

Strictly speaking, it is not possible "convert" QuickDraw GX pictures to QuickDraw PICTs without loss of information because GX has much greater functionality than traditional QuickDraw. There is no way to represent complex transfer modes, perspective, advances typography, etc. using the QuickDraw imaging model. By using this codec, you do not lose any of these features.

If you looking for information on how to use the codec, here is sample code. This file (which was accidentally omitted from the last SDK) called "DecompressShape.c" contains all the code you need to embed a GX shape into a PICT using the new codec. The important routine in this file is:

   PicHandle DecompressShape (gxShape theShape, PicHandle proxie,
                  Boolean forPrintingOnly, Boolean eraseBackground)

A proxie can also be embedded so that the PICT can be viewed on machines without GX. On the September 95 GX SDK, this source file will be added to the GX Libraries folder (it may be renamed however to give abetter indication of its function). Note that the "DecompressShape.c" technique is quite different from that used in the older "PicturesAndPICTLibrary.c" which embeds a GX shape into a PICT using picComments. We recommend you use DecompressShape because picComments have several weaknesses including:

  1. they are limited to 32K
  2. many applications strip out any picComments they don't recognize.
  3. DrawPicture() ignores all picComments
By using the codec to embed the GX picture, all these problems are avoided.

/*
	File:		DecompressShape.h

	Contains:	graphics libraries - shape decompression

	Written by:	Mike Reed

	Copyright:	(c) 1995 by Apple Computer, Inc., all rights reserved.

	Writers:

		(jtd)	John Daggett

	Change History (most recent first):

		 <1>	 9/14/95	jtd		First checked in.
*/

#pragma once
#ifndef decompressShapeIncludes
	#define decompressShapeIncludes

	#include <QuickDraw.h>	#include <GXTypes.h>
	#ifdef __cplusplus
	extern "C" {
	#endif
	
	Handle CreateQDGXStream(gxShape source, PicHandle proxie, Boolean forPrintingOnly, Boolean eraseBackground);
	PicHandle DecompressShape(gxShape theShape, PicHandle proxie, Boolean forPrintingOnly, Boolean eraseBackground);
	PicHandle ShapeToPICT(gxShape source);
	void ShapeToScrap(gxShape source, Boolean addProxie, Boolean forPrintingOnly, Boolean eraseBackground);
	void DragAndDropShape(EventRecord* event, gxShape shape);

	#ifdef __cplusplus
	};
	#endif
#endif


/*
	File:		DecompressShape.c

	Contains:	graphics libraries - shape decompression

	Written by:	Mike Reed

	Copyright:	(c) 1995 by Apple Computer, Inc., all rights reserved.

	Writers:

		(jtd)	John Daggett

	Change History (most recent first):

		 <2>	 9/14/95	jtd		replaced boolean with Boolean
		 <1>	 9/14/95	jtd		First checked in.
*/

#include <Drag.h>#include <Gestalt.h>#include <ImageCompression.h>#include <Memory.h>#include <Scrap.h>
#include <GXTypes.h>#include <GXMath.h>#include <GXGraphics.h>#include <GXEnvironment.h>#include "StorageLibrary.h"
#include "DecompressShape.h"

#define LONGALIGN(n)		(((n) + 3) & ~3L)
#define kAtomHeaderSize		(sizeof(Size) + sizeof(OSType))

static void RectangleToRect(const gxRectangle* gxr, Rect* qdr)
{
	qdr->left = FixedRound(gxr->left);
	qdr->top = FixedRound(gxr->top);
	qdr->right = FixedRound(gxr->right);
	qdr->bottom = FixedRound(gxr->bottom);
}

static long* AppendAtom(long stream[], Size size, OSType tag, const void* data)
{
#ifdef debugging
	if (size & 3)
		DebugStr("\patom size needs to be long aligned");
#endif

	*stream++	= size + kAtomHeaderSize;
	*stream++	= tag;
	BlockMove(data, (Ptr)stream, size);

	return (long*)((char*)stream + size);
}

/*
 *	See the comment on DecompressShape for an explaination of the parameters.
 *	This routine is used by both DecompressShape for embedding shapes in PICTs,
 *	and AddQDGXRecorderFrame for making gx movies.
*/
Handle CreateQDGXStream(gxShape source, PicHandle proxie, Boolean forPrintingOnly, Boolean eraseBackground)
{
	#define			gxForPrintingOnlyAtom	'fpto'
	#define			gxEraseBackgroundAtom	'erbg'

	long				atomCount, shapeSize, proxieSize, dataSize, fontListSize, eraseSize;
	Handle			dataHdl, shapeHdl;
	gxFlatFontList*		fontList;
	gxTag			fontListTag;

#ifdef debugging
	GXIgnoreGraphicsWarning(tags_of_type_flst_removed);
#endif
	shapeHdl = ShapeToHandleWithFlags(source, gxFontListFlatten | gxFontGlyphsFlatten | gxFontVariationsFlatten);
#ifdef debugging
	GXPopGraphicsWarning();
#endif
	if (shapeHdl == nil)
		return nil;

	if (proxie)
	{	atomCount = 2;
		proxieSize = LONGALIGN(GetHandleSize((Handle)proxie));
	}
	else
	{	atomCount = 1;
		proxieSize = 0;
	}
	shapeSize = LONGALIGN(GetHandleSize(shapeHdl));

	if (forPrintingOnly)
		++atomCount;
	if (eraseBackground)
		++atomCount;

	fontListSize = 0;
	fontList = nil;
	GXIgnoreGraphicsWarning(count_out_of_range);
	if (GXGetShapeTags(source, gxFlatFontListItemTag, 1, 1, &fontListTag) > 0)
	{	fontListSize = GXGetTag(fontListTag, nil, nil);
		if (fontListSize > 0)
		{	fontList = (gxFlatFontList*)NewPtr(fontListSize);
			if (fontList != nil)
			{	GXGetTag(fontListTag, nil, fontList);
				fontListSize = LONGALIGN(fontListSize);
				++atomCount;
			}
			else
				fontListSize = 0;
		}
	}
	GXPopGraphicsWarning();		// count_out_of_range

	dataSize = atomCount * kAtomHeaderSize + shapeSize + proxieSize + fontListSize + sizeof(long);
	dataHdl = NewHandle(dataSize);
	if (dataHdl == nil)
	{	DisposHandle(shapeHdl);
		if (fontList)
			DisposPtr((Ptr)fontList);
		return nil;
	}
	
	{	long* p = (long*)*dataHdl;

		if (forPrintingOnly)
			p = AppendAtom(p, 0, gxForPrintingOnlyAtom, nil);
		if (eraseBackground)
			p = AppendAtom(p, 0, gxEraseBackgroundAtom, nil);
		if (proxie)
			p = AppendAtom(p, proxieSize, 'PICT', *proxie);
		if (fontList)
			p = AppendAtom(p, fontListSize, gxFlatFontListItemTag, fontList);
		p = AppendAtom(p, shapeSize, 'qdgx', *shapeHdl);
		*p++ = 0;		// end of the atom-list

		DisposHandle(shapeHdl);
		if (fontList)
			DisposPtr((Ptr)fontList);
	}
	return dataHdl;
}

static void GetRidOfAnyQDShapeTags(gxShape shape)
{
	gxShapeType shapeType = GXGetShapeType(shape);

	if (shapeType == gxPictureType)
	{	long		index, count;
		gxShape*	subShapes;
	
		count = GXGetPicture(shape, nil, nil, nil, nil);
		if (count > 0)
		{	subShapes = (gxShape*)NewPtr(count * sizeof(gxShape));
			if (subShapes != nil)
			{	GXGetPicture(shape, subShapes, nil, nil, nil);
				for (index = 0; index < count; index++)
					GetRidOfAnyQDShapeTags(subShapes[index]);
				DisposPtr((Ptr)subShapes);
			}
		}
	}
	else if (shapeType == gxRectangleType && GXGetShapeTags(shape, gxQuickDrawPictTag, 1, gxSelectToEnd, nil) > 0)
		GXSetShapeType(shape, gxPictureType);
}

/*
 *	This guy returns a Quickdraw picture containing an embedded shape, and a proxie
 *	of the shape, if proxie is not nil. This is called by ShapeToScrap and DragAndDropShape.
 *
 *	theShape			* the shape you want to embedd in a PICT
 *	proxie			* a PICT to be drawn if theShape cannot be drawn (optional but recommended)
 *	forPrintingOnly		* if TRUE, then the decompressor will always look for the proxie
 *					and theShape will only be used when printing. Use this setting if
 *					theShape might be too large or too slow when drawn from other apps.
 *					* If FALSE, then the decompressor will draw theShape unless it
 *					gets an error, in which case it will look for a proxie.
 *	eraseBackground	* if TRUE, the decompressor will always erase the background to WHITE
 *					before drawing the shape. This is slower, but needed if the shape does not
 *					fill its bounding rectangle.
 *					* if FALSE, the decompressor will just draw the shape. Use this setting
 *					if the shape entirely fills its bounding rectangle.
 *
 *	The shape [and proxie] is embedded by constructing a stream of atoms. Each atom begins
 *	with a size (long) and a type (OSType) and then the data for that type. After the last atom,
 *	there is a trailing zero (long) to mark the end of the stream. For embedded shapes, the type
 *	is 'qdgx', and for the proxie the type is 'PICT'. Note that the size fields are rounded up to
 *	a multiple of 4. Finally, to alert QuickTime that the data is in this parsable form with a
 *	possible PICT proxie, we add a 'prxy' extension to the ImageDescriptionHandle.
 *
 *	Picture of this form will draw the embedded shape when an application calls DrawPicture
 *	if GX is around, and if not, the proxie will be drawn. When printed, the shape or the proxie
 *	will be printed. This is meant to replace the PicComment described in GX 1.0 for embedding
 *	shapes in pictures.
 *
 *	If you want to include a flatFontList tag, be sure that theShape is a picture, otherwise GX will
 *	not return the tag after GXFlattenShape. The flatFontList tag makes certain printing conditions
 *	more efficient (i.e. font downloading to postscript printers).
 *
 *	Your shape must not contain a gxQuickDrawPictTag, meaning it contains embedded QD data, becuase
 *	this will potentially crash when it tries to print. To fix that, DecompressShape looks for occurrances
 *	of the tag, and converts them to real gx data by calling GXSetShapeType(shape, gxPictureType).
*/
PicHandle DecompressShape(gxShape theShape, PicHandle proxie, Boolean forPrintingOnly, Boolean eraseBackground)
{
	#define				kQuickTimeGestalt		'qtim'

	PicHandle				thePicture;
	ImageDescriptionHandle	descHdl;
	ImageDescriptionPtr		descPtr;
	Handle				dataHdl;
	long					version;

	if (Gestalt(kQuickTimeGestalt, &version) != noErr)
		return nil;

	GetRidOfAnyQDShapeTags(theShape);

	/*
	 *	Move the shape's topLeft to 0,0 so that it draws neatly inside the picture frame.
	 *	Note that the qdgx movie library does not move the shape, since the shape may not
	 *	take up the whole frame.
	*/
	{	gxRectangle	bounds;

		GXGetShapeLocalBounds(theShape, &bounds);
		if (bounds.left || bounds.top)
			GXMoveShape(theShape, -bounds.left, -bounds.top);
		dataHdl = CreateQDGXStream(theShape, proxie, forPrintingOnly, eraseBackground);
		if (bounds.left || bounds.top)
			GXMoveShape(theShape, bounds.left, bounds.top);
	}
	if (dataHdl == nil)
		return nil;

	descHdl = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription));
	if (descHdl)
	{	Rect			shortBounds;
		gxRectangle	bounds;

		GXGetShapeLocalBounds(theShape, &bounds);
		RectangleToRect(&bounds, &shortBounds);
		OffsetRect(&shortBounds, -shortBounds.left, -shortBounds.top);	// set the topLeft of the src to 0,0
		thePicture = OpenPicture(&shortBounds);

		descPtr = *descHdl;
		descPtr->idSize = sizeof(ImageDescription);
		descPtr->cType = 'qdgx';
		descPtr->vendor = 'appl';
		descPtr->temporalQuality = codecLosslessQuality;
		descPtr->width = shortBounds.right;
		descPtr->height = shortBounds.bottom;
		descPtr->hRes = descPtr->vRes = ff(72);
		descPtr->dataSize = GetHandleSize(dataHdl);
		descPtr->frameCount = 1;
		descPtr->depth = 32;
		descPtr->clutID = -1;

		//	If there is a PICT proxie, add an image extension to tell QuickTime, in case GX is not around.
		if (proxie)
		{	Handle prxyVersionHdl = NewHandle(sizeof(long));

			if (prxyVersionHdl != nil)
			{	*(long*)*prxyVersionHdl = 0;		// version number for 'prxy' extension
				SetImageDescriptionExtension(descHdl, prxyVersionHdl, 'prxy');
			}
		}

		HLock(dataHdl);
		DecompressImage(*dataHdl, descHdl, ((CGrafPtr)qd.thePort)->portPixMap, &shortBounds, &shortBounds, srcCopy, nil);
		DisposeHandle((Handle)descHdl);
		ClosePicture();
	}
	else
		thePicture = nil;

	DisposHandle(dataHdl);

	return thePicture;
}

/*
 *	This guy returns a Quickdraw picture containing a 1-bit bitmap of the shape.
 *	This is used by ShapeToScrap to create a proxie when calling DecompressShape.
 *	If you want to make the proxie prettier (and larger), change the bitmap to 8-bit.
 *	However, if you're using this in conjunction with DecompressShape to place a
 *	gxShape on the clipboard, 1-bit should be enough, since the actual shape will be
 *	drawn, rather than the proxie (unless forPrintingOnly is true).
*/
PicHandle ShapeToPICT(gxShape source)
{
	gxRectangle	bounds;
	gxShape		bitShape;
	gxBitmap		bitmap;
	PicHandle		thePicture;
	Rect			shortBounds;

	/*
	 *	GetShapeLocalBounds doesn't accurately report the bounds of a gxQuickDrawPictTag.
	 *	One option is to convert the tags to real GX pictures.
	*/
	GetRidOfAnyQDShapeTags(source);

	GXGetShapeLocalBounds(source, &bounds);
	RectangleToRect(&bounds, &shortBounds);
	OffsetRect(&shortBounds, -shortBounds.left, -shortBounds.top);

	bitmap.width		= shortBounds.right;
	bitmap.height		= shortBounds.bottom;
	bitmap.rowBytes	= bitmap.width + 31 >> 5 << 2;
	bitmap.pixelSize	= 1;
	bitmap.space		= gxIndexedSpace;
	bitmap.set			= nil;
	bitmap.profile		= nil;
	bitmap.image		= NewPtrClear(bitmap.rowBytes * bitmap.height);
	if (bitmap.image == nil)
		return nil;

	bitShape = GXNewBitmap(&bitmap, nil);
	if (bitShape != nil)
	{	gxViewGroup group	= GXNewViewGroup();
		gxViewDevice device	= GXNewViewDevice(group, bitShape);
		gxViewPort port	= GXNewViewPort(group);
		gxTransform trans	= GXCloneTransform(GXGetShapeTransform(source));

		GXSetShapeAttributes(source, GXGetShapeAttributes(source) | gxMapTransformShape);
		GXMoveShape(source, -bounds.left, -bounds.top);
		GXSetViewPortDither(port, 4);
		GXSetShapeViewPorts(source, 1, &port);
		GXDrawShape(source);
		GXSetShapeTransform(source, trans);
		GXDisposeTransform(trans);
		
		GXDisposeViewGroup(group);	/* this disposes the gxViewPort and gxViewDevice */
		GXDisposeShape(bitShape);
	}

	{	GrafPtr	thePort;
		BitMap	srcBits;
	
		GetPort(&thePort);
		srcBits.baseAddr = bitmap.image;
		srcBits.rowBytes = bitmap.rowBytes;
		srcBits.bounds = shortBounds;

		thePicture = OpenPicture(&shortBounds);
		CopyBits(&srcBits, &thePort->portBits, &shortBounds, &shortBounds, srcOr, nil);
		ClosePicture();
	}
	
	DisposPtr((Ptr)bitmap.image);
	
	return thePicture;
}

/*
 *	This guy puts a Quickdraw picture on the clipboard containing an embedded shape
 *	and, if addProxie is true, a 1-bit bitmap of the shape. Call this in response to
 *	the user choosing "Copy" or "Cut" from the Edit menu. See comment for DecompressShape
 *	to explain forPrintingOnly.
*/
void ShapeToScrap(gxShape source, Boolean addProxie, Boolean forPrintingOnly, Boolean eraseBackground)
{
	PicHandle	picture, proxie;

	proxie = addProxie ? ShapeToPICT(source) : nil;
	picture = DecompressShape(source, proxie, forPrintingOnly, eraseBackground);
	if (proxie)
		KillPicture(proxie);
	if (picture)
	{	HLock((Handle)picture);
		ZeroScrap();
		PutScrap(GetHandleSize((Handle)picture), 'PICT', (Ptr)*picture);
		KillPicture(picture);
	}
}

/*
 *	The ItemReference is the gxShape to be sent. The dragSendRefCon is ignored.
*/
static pascal OSErr LibrarySendDataProc(FlavorType theType, void *dragSendRefCon,
								ItemReference theItem, DragReference theDrag)
{
	OSErr	result = noErr;
	gxShape	shape = (gxShape)theItem;

	switch (theType) {
	case 'qdgx':
	{	Handle flat = ShapeToHandle(shape);

		if (flat)
		{	HLock(flat);
			result = SetDragItemFlavorData(theDrag, theItem, 'qdgx', *flat, GetHandleSize(flat), 0);
			DisposHandle(flat);
		}
		break;
	}
	case 'PICT':
	{	PicHandle proxie = ShapeToPICT(shape);
		PicHandle pict = DecompressShape(shape, proxie, false, true);

		if (proxie)
			KillPicture(proxie);
		if (pict)
		{	HLock((Handle)pict);
			result = SetDragItemFlavorData(theDrag, theItem, 'PICT', (Ptr)*pict, GetHandleSize((Handle)pict), 0);
			KillPicture(pict);
		}
		break;
	}
	default:
		result = badDragFlavorErr;


Technical Support
Technical Q&As
Previous Question | Contents | Next Question

Navigation graphic, see text links

Main | Page One | What's New | Apple Computer, Inc. | Find It | Contact Us | Help